package cn.iocoder.yudao.module.resource.service.keyword;

import cn.hutool.dfa.WordTree;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.redis.core.RedisPublishClient;
import cn.iocoder.yudao.module.resource.controller.admin.keyword.vo.KeyWordPageReqVO;
import cn.iocoder.yudao.module.resource.controller.admin.keyword.vo.KeyWordSaveReqVO;
import cn.iocoder.yudao.module.resource.dal.dataobject.keyword.KeyWordDO;
import cn.iocoder.yudao.module.resource.dal.mysql.keyword.KeyWordMapper;
import cn.iocoder.yudao.module.resource.events.RefreshKeywordEvent;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.resource.enums.ErrorCodeConstants.KEY_WORD_HAD_EXISTS;
import static cn.iocoder.yudao.module.resource.enums.ErrorCodeConstants.KEY_WORD_NOT_EXISTS;

/**
 * 关键字 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class KeyWordServiceImpl implements KeyWordService {
    private WordTree tree;
    private static final long CACHE_EXPIRE_TIME = 1000L * 60 * 60;

    @Resource
    private KeyWordMapper keyWordMapper;
    @Resource
    private RedisPublishClient redisPublishClient;

    @PostConstruct
    public void init() {
        refreshKeywordCache(null);
    }

    @EventListener
    public void refreshKeywordCache(RefreshKeywordEvent event) {
        keyWordDOMapCache.refresh(getClass().getName());
    }

    @Getter
    private final LoadingCache<String, Map<Integer, KeyWordDO>> keyWordDOMapCache = CacheUtils.buildCache(Duration.ofMinutes(60L),
            new CacheLoader<>() {

                @Override
                public Map<Integer, KeyWordDO> load(String id) {
                    final List<KeyWordDO> tagDOList = keyWordMapper.selectList();
                    final Map<Integer, KeyWordDO> keyWordDOMap = tagDOList.stream().collect(Collectors.toMap(KeyWordDO::getId, c -> c));

                    final WordTree tree = new WordTree();
                    keyWordDOMap.forEach((k, v) -> {
                        tree.addWord(v.getKeyWord());
                    });

                    KeyWordServiceImpl.this.tree = tree;
                    log.info("刷新关键字缓存");
                    return keyWordDOMap;
                }
            });

    @Override
    public Integer createKeyWord(KeyWordSaveReqVO createReqVO) {
        KeyWordDO keyWordDO = keyWordMapper.getOne(createReqVO.getKeyWord());
        if (keyWordDO != null) {
            throw exception(KEY_WORD_HAD_EXISTS);
        }
        // 插入
        KeyWordDO keyWord = BeanUtils.toBean(createReqVO, KeyWordDO.class);
        keyWordMapper.insert(keyWord);

        RefreshKeywordEvent keywordEvent = new RefreshKeywordEvent();
        redisPublishClient.publishMsg(keywordEvent);
        // 返回
        return keyWord.getId();
    }

    @Override
    public void checkKeyWordExist(List<Integer> idList) {
        for (Integer id : idList) {
            this.validateKeyWordExists(id);
        }
    }

    @Override
    public void updateKeyWord(KeyWordSaveReqVO updateReqVO) {
        // 校验存在
        validateKeyWordExists(updateReqVO.getId());
        // 更新
        KeyWordDO updateObj = BeanUtils.toBean(updateReqVO, KeyWordDO.class);
        keyWordMapper.updateById(updateObj);
        RefreshKeywordEvent keywordEvent = new RefreshKeywordEvent();
        redisPublishClient.publishMsg(keywordEvent);
    }

    @Override
    public void deleteKeyWord(Integer id) {
        // 校验存在
        validateKeyWordExists(id);

        // 删除
        keyWordMapper.deleteById(id);
        RefreshKeywordEvent keywordEvent = new RefreshKeywordEvent();
        redisPublishClient.publishMsg(keywordEvent);
    }

    private void validateKeyWordExists(Integer id) {
        if (keyWordMapper.selectById(id) == null) {
            throw exception(KEY_WORD_NOT_EXISTS);
        }
    }

    @Override
    public KeyWordDO getKeyWord(Integer id) {
        return keyWordMapper.selectById(id);
    }

    @Override
    public List<KeyWordDO> getKeywordListByIds(Collection<Integer> ids) {
        return keyWordMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<KeyWordDO> getKeyWordPage(KeyWordPageReqVO pageReqVO) {
        return keyWordMapper.selectPage(pageReqVO);
    }

    @Override
    public List<KeyWordDO> getKeyWordList(List<Integer> keywordIdList) {
        if (keywordIdList == null || keywordIdList.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        return keyWordMapper.selectBatchIds(keywordIdList);

    }

    @Override
    public List<KeyWordDO> getKeywordListByNames(List<String> names) {
        return keyWordMapper.selectList(KeyWordDO::getKeyWord, names);
    }

    // https://doc.hutool.cn/pages/dfa/index/#%E7%94%B1%E6%9D%A5
    // 使用DFA查找关键字提升性能
    @Override
    public List<String> getKeywordListByText(String text) {
        getKeyWordDOMapCache().getUnchecked(getClass().getName());

        final List<String> keywordList = tree.matchAll(text, -1, true, true);
        return keywordList;
    }

    @Override
    public List<String> getKeyWordListByIds(Collection<Integer> ids) {
        Map<Integer, KeyWordDO> cache = getKeyWordDOMapCache().getUnchecked(getClass().getName());

        List<String> keywordList = new ArrayList<>(ids.size());
        for (Integer id : ids) {
            KeyWordDO keyWordDO = cache.get(id);
            if (keyWordDO != null) {
                keywordList.add(keyWordDO.getKeyWord());
            }
        }
        return keywordList;
    }

}